package cn.ly.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 方式二：解决方式一的羊群效应问题
 * 使用Zk的有序节点，都只监听自己前面一个节点的通知。不再一窝蜂的监听同一个节点后去抢锁
 */
public class ZkLock2 extends AbstractLock {
    //ZK服务器连接地址
    private static final String CONNECT_IP = "127.0.0.1:2181";
    //创建ZK连接
    private ZkClient zkClient = new ZkClient(CONNECT_IP);
    //【注意】：要预先在Zookeeper中创建该父节点
    private static final String LOCK_NODE = "/lock2";
    private String beforePath;//当前请求的节点前一个节点
    private String currentPath;//当前请求的节点

    @Override
    public boolean tryLock() {
        //如果currentPath为空则为第一次尝试加锁，第一次加锁赋值currentPath
        if (currentPath == null || currentPath.length() <= 0) {
            //创建一个临时顺序节点：/lock2/0000000000
            currentPath = this.zkClient.createEphemeralSequential(LOCK_NODE + '/', "lock");
        }
        //获取所有临时节点并排序，临时节点名称为自增长的字符串如：0000000001
        List<String> childrens = this.zkClient.getChildren(LOCK_NODE);
        Collections.sort(childrens);
        if (currentPath.equals(LOCK_NODE + '/' + childrens.get(0))) {
            //如果当前节点在所有节点中排名第一则获取锁成功
            return true;
        } else {
            //如果当前节点在所有节点中排名中不是排名第一，则获取前面的节点名称，并赋值给beforePath
            int wz = Collections.binarySearch(childrens, currentPath.substring(7));
            beforePath = LOCK_NODE + '/' + childrens.get(wz - 1);
        }
        return false;
    }

    @Override
    public void waitLock() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataDeleted(String s) throws Exception {
                countDownLatch.countDown();
            }
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
            }
        };
        //注册监听【监听的是前一个节点】
        zkClient.subscribeDataChanges(beforePath, listener);
        if (zkClient.exists(beforePath)) {
            try {
                //线程阻塞
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        System.out.println(Thread.currentThread().getName()+">>>>>>>线程被唤醒");
        zkClient.unsubscribeDataChanges(beforePath, listener);
    }


    @Override
    public void unLock() {
        //删除当前临时节点
        zkClient.delete(currentPath);
        zkClient.close();
    }
}
